#include <stdio.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>

int flush_encoder(AVFormatContext *ofmt_ctx, AVCodecContext * pCodecCtx,  int stream_index)
{
    AVCodecParameters *codecpar  = ofmt_ctx->streams[stream_index]->codecpar;
    int got_fame = 0;
    AVPacket *pkt = av_packet_alloc();
    int ret = 0;
    while (1)
    {      
        av_init_packet(pkt);
        pkt->data = NULL;
        pkt->size = 0;
        if (avcodec_send_frame(pCodecCtx, NULL) < 0)
        {
            printf("fail to send frame\n");
            return -1;
        }

        // 第十四步  avcodec_receive_packet  获取到编码数据  并通过av_write_frame写入aac文件
        while (avcodec_receive_packet(pCodecCtx, pkt) >= 0)
        {
            pkt->stream_index = stream_index;
            av_write_frame(ofmt_ctx, pkt);
            av_packet_unref(pkt);
        }
       
        if (ret < 0)
        {
            break;
        }
    }
    av_packet_free(&pkt);
    return 0;
}

int main(int argc, char *argv[])
{
    int ret = 0;
    const char *input;
    const char *output;
    input = argv[1];
    output = argv[2];
    // 第一步获取AVOutputFormat
    AVOutputFormat *oformat = av_guess_format(NULL, output, NULL);
    if (oformat == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to find the output format\n");
        ret = -1;
        goto __ERROR;
    }

    // 第二步创建我们要创建的aac文件的上下文AVFormatContext *ofmt_ctx
    AVFormatContext *ofmt_ctx = avformat_alloc_context();
    if (avformat_alloc_output_context2(&ofmt_ctx, oformat, oformat->name, output) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to alloc output context\n");
        ret = -1;
        goto __ERROR;
    }

    //第三步 创建编码器
    AVCodec *pCodec = avcodec_find_encoder_by_name("libfdk_aac");
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
    pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
    pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16; //其他会出错
    pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
    pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
    pCodecCtx->sample_rate = 44100;
    pCodecCtx->bit_rate = 0;
    pCodecCtx->profile = FF_PROFILE_AAC_HE_V2;

    //第四步创建输出流并设置输出流的编码参数
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, pCodec);
    avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx);
    if (out_stream == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to create new stream\n");
        ret = -1;
        goto __ERROR;
    }
    if (pCodec == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to find codec\n");
        ret = -1;
        goto __ERROR;
    }

    // 第五步 打开编码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to open codec\n");
        ret = -1;
        goto __ERROR;
    }
    av_dump_format(ofmt_ctx, 0, output, 1);

    // 第六步 获取输出aac文件的AVIOContext
    if (avio_open(&ofmt_ctx->pb, output, AVIO_FLAG_WRITE) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to open output\n");
        ret = -1;
        goto __ERROR;
    }

    // 第七步写aac文件头
    if (avformat_write_header(ofmt_ctx, NULL) < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "fail to write header");
        ret = -1;
        goto __ERROR;
    }

    //第八步创建AVFrame ,设置声道数，位深，单通道采样数，为pFrame中的data分配空间
    AVFrame *pframe = av_frame_alloc();
    pframe->channels = pCodecCtx->channels;
    pframe->format = pCodecCtx->sample_fmt;
    pframe->nb_samples = pCodecCtx->frame_size;
    int size = av_samples_get_buffer_size(NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 1);
    uint8_t *out_buffer = (uint8_t *)av_malloc(size);
    avcodec_fill_audio_frame(pframe, pCodecCtx->channels, pCodecCtx->sample_fmt, (const uint8_t *)out_buffer, size, 1);
    
    // 第九步 初始化重采样上下文,设置重采样的输入格式输出格式
    SwrContext *pSwrCtx = swr_alloc();
    swr_alloc_set_opts(pSwrCtx, pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate,
                       pCodecCtx->channel_layout, AV_SAMPLE_FMT_S16, 44100, 0, NULL);
    swr_init(pSwrCtx);
    /*重采样后的数据保存的缓冲区*/
    uint8_t **data = (uint8_t **)av_calloc(pCodecCtx->channels, sizeof(*data));
    av_samples_alloc(data, NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 1);



    AVPacket *pkt = av_packet_alloc();
    av_new_packet(pkt, size);
    pkt->data = NULL;
    pkt->size = 0;

    FILE *fp = fopen(input, "rb");
    if (fp == NULL)
    {
        printf("fail to open file\n");
        ret = -1;
        goto __ERROR;
    }
    int count = 1;
    while (1)
    {
    // 第十步 从输入文件读取单通道采样数 * 通道数 * 位深字节数大小的数据     
       size = pframe->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * pframe->channels;
        if (fread(out_buffer, 1, size, fp) < 0)
        {
            printf("fail to read raw data\n");
            ret = -1;
            goto __ERROR;
        }
        else if (feof(fp))
        {
            break;
        }

        // 第十一步 对读取的数据进行重采样
        swr_convert(pSwrCtx, data, pCodecCtx->frame_size, (const uint8_t **)pframe->data, pframe->nb_samples);
        size = pCodecCtx->frame_size * av_get_bytes_per_sample(pCodecCtx->sample_fmt);
        memcpy(pframe->data[0], data[0], size);
        memcpy(pframe->data[1], data[1], size);
        
        // 第十二步  设置pts，表明当前数据在aac的位置
        pframe->pts = count * 100;

        // 第十三步 avcodec_send_frame 发送数据到编码器
        if (avcodec_send_frame(pCodecCtx, pframe) < 0)
        {
            printf("fail to send frame\n");
            ret = -1;
            goto __ERROR;
        }
        
        // 第十四步  avcodec_receive_packet  获取到编码数据  并通过av_write_frame写入aac文件
        while (avcodec_receive_packet(pCodecCtx, pkt) >= 0)
        {
            pkt->stream_index = out_stream->index;
            av_log(NULL, AV_LOG_INFO, "write %d frame\n", count);
            av_write_frame(ofmt_ctx, pkt);
            av_packet_unref(pkt);
        }
        count++;
       
    }
    // 第十六步 flush_encoder
    flush_encoder(ofmt_ctx, pCodecCtx, out_stream->index);

    // 第十七步  av_write_trailer
    av_write_trailer(ofmt_ctx);

__ERROR:
    // 第十八步  释放资源 
    if (pkt)
    {
        av_packet_free(&pkt);
    }
    if (pSwrCtx)
    {
        swr_free(&pSwrCtx);
    }
    if (out_buffer)
    {
        av_free(out_buffer);
    }
    if (pframe)
    {
        av_frame_free(&pframe);
    }

    if (ofmt_ctx->pb)
    {
        avio_close(ofmt_ctx->pb);
    }

    if (pCodec)
    {
    }

    if (pCodecCtx)
    {
        avcodec_close(pCodecCtx);
        avcodec_free_context(&pCodecCtx);
    }

    if (ofmt_ctx)
    {
        avformat_free_context(ofmt_ctx);
    }

    return 0;
}